using System;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.Web;
using System.Web.Configuration;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Reputation.Services;
using Microsoft.Win32;
using System.Security.Cryptography.X509Certificates;

namespace MRS.Delivery
{
    public partial class QoSPage : System.Web.UI.Page
    {
        private const string probeRegistryKey = @"SOFTWARE\Microsoft\Operations\ProbeFile";
        private const string probeFileKey = "PreConfigSuccess";
        private const int probeKeepAliveValue = 1;
        private const int qosReturnCodeOffline = 503;
        private const int qosReturnCodeOnline = 200;
        private const string qosResponse = "<html><head><title>QOS</title></head><body style=\"font-family:Verdana;\"><p>This is a QOS Page. MRS Delivery Service is working correctly.</p><table><tr><td>Status:</td><td style=\"font-weight:bold\">{0}</td></tr><tr><td>Status Description:</td><td style=\"font-weight:bold\">{1}</td></tr></table></body></html>";
        private static readonly string PageContentOnline = string.Format(qosResponse, qosReturnCodeOnline.ToString(), "<span style=\"color:#008000\">Online</span>");
        private static readonly string PageContentConfiguredOffline = string.Format(qosResponse, qosReturnCodeOffline.ToString(), "<span style=\"color:#F00\">Configured Offline</span>");
        private static readonly string PageContentDSOffline = string.Format(qosResponse, qosReturnCodeOffline.ToString(), "<span style=\"color:#F00\">Delivery Service Offline</span>");

        private const string LookupCategoriesForUrlCacheKey = "LookupCategoriesForUrlApiEnabled";
        private const string WcfHttpsEnabledKey = "WcfHttpsEnabled";
        private const string SmokeTestKey = "smoke";

        // Needed Locks for the HttpRuntime.Cache
        private static object WcfHttpsEnabledLock = new object();

        private static System.Net.Security.RemoteCertificateValidationCallback defaultCertificateCallback = System.Net.ServicePointManager.ServerCertificateValidationCallback;

        private enum DSMethods
        {
            LookupObjects = 1,
            LookupCategoriesForUrl = 2
        };

        protected void Page_Load(object sender, System.EventArgs e)
        {
            HttpContext MyHttpContext = HttpContext.Current;

            if (MyHttpContext != null &&
                MyHttpContext.Request != null)
            {
                MyHttpContext.Response.Buffer = true;

                int MyStatusCode = qosReturnCodeOffline;
                string ServiceOfflineReturnMessage = String.Empty;

                try
                {
                    // Determine if we should return a _qosReturnCodeOffline due to ProbeFile registry settings.
                    using (RegistryKey probeKey = Registry.LocalMachine.OpenSubKey(probeRegistryKey))
                    {
                        if (probeKey != null &&
                            (int)probeKey.GetValue(probeFileKey, 99999) == probeKeepAliveValue)
                        {

                            //MyHttpContext.Response.Write("Getting " + _probeFileKeyName);
                            //MyHttpContext.Response.Write(probeKey.GetValue(_probeFileKeyName, 99999));

                            MyStatusCode = qosReturnCodeOnline;
                        }
                        else
                        {
                            ServiceOfflineReturnMessage = PageContentConfiguredOffline;
                        }
                    }
                }
                catch 
                {
                    ServiceOfflineReturnMessage = PageContentConfiguredOffline;
                }

                // Now determine if the Delivery Service is up and running
                if (MyStatusCode == qosReturnCodeOnline)
                {
                    if (IsDeliveryServiceOnline() == false)
                    {
                        MyStatusCode = qosReturnCodeOffline;
                        ServiceOfflineReturnMessage = PageContentDSOffline;
                    }
                }

                // Off goes the response
                MyHttpContext.Response.StatusCode = MyStatusCode;
                MyHttpContext.Response.Write((MyStatusCode == qosReturnCodeOnline) ? PageContentOnline : ServiceOfflineReturnMessage);
                MyHttpContext.Response.Flush();
            }
        }

        private bool IsDeliveryServiceOnline()
        {
            int iterationCount = Microsoft.Reputation.Services.Properties.Settings.Default.QoSPageIterations;

            // Override the certificate validation to allow the server name to differ from the global 
            // name that we use for requests that are sent to the F5.
            System.Net.ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;

            // For normal qos page runs we only want to return a failure if all of the 
            // iterations fail when calling the Delivery Service.  If one passes we are
            // still considered "up"
            int allowedPassCount = 1;
            int iterationsThatPassed = 0;

            // If we have the "smoke" flag in any form we want to run the smoke style test.
            // The logic of this is that all of the iterations must pass or we want to fail
            // and be in a "down" state
            if ((Request.Form[SmokeTestKey] ?? Request.QueryString[SmokeTestKey]) != null)
            {
                allowedPassCount = iterationCount;
            }

            for (int i = 0; i < iterationCount; i++)
            {
                bool testPassed = CallDeliveryService(DSMethods.LookupObjects);

                if (testPassed && LookupCategoriesForUrlEnabled())
                {
                    testPassed = CallDeliveryService(DSMethods.LookupCategoriesForUrl);
                }
                if (testPassed)
                {
                    iterationsThatPassed++;
                }
            }

            return iterationsThatPassed >= allowedPassCount;
        }

        private bool UseHTTPSConnection()
        {
            if (null == HttpRuntime.Cache[WcfHttpsEnabledKey])
            {
                lock (WcfHttpsEnabledLock)
                {
                    if (null == HttpRuntime.Cache[WcfHttpsEnabledKey])
                    {
                        bool httpsEnabled = false;

                        string webConfig = Server.MapPath("~/web.config");
                        XElement xElement = XElement.Load(webConfig);

                        string xPathToSecurity = Microsoft.Reputation.Services.Properties.Settings.Default.XPathToWCFSecurity;
                        XElement securityElement = System.Xml.XPath.Extensions.XPathSelectElement(xElement, xPathToSecurity);

                        if (securityElement != null)
                        {
                            XAttribute xAttribute = securityElement.Attribute("mode");

                            if (xAttribute != null && "Transport".Equals(xAttribute.Value))
                            {
                                httpsEnabled = true;
                            }
                        }
                        Cache.Insert(WcfHttpsEnabledKey,
                            httpsEnabled,
                            null,
                            DateTime.Now.AddDays(1),
                            System.Web.Caching.Cache.NoSlidingExpiration,
                            System.Web.Caching.CacheItemPriority.NotRemovable,
                            null);
                    }
                }
            }
            return (bool)HttpRuntime.Cache[WcfHttpsEnabledKey];
        }

        /// <summary>
        /// Check the flag in the Delivery Service web.config to see if the api is enabled
        /// Cache the result for a day
        /// </summary>
        /// <returns>true if the api is enabled</returns>
        private bool LookupCategoriesForUrlEnabled()
        {
            if (null == HttpRuntime.Cache[LookupCategoriesForUrlCacheKey])
            {
                lock (LookupCategoriesForUrlApiEnabledLock)
                {
                    if (null == HttpRuntime.Cache[LookupCategoriesForUrlCacheKey])
                    {
                        bool urlApiEnabled = DeliveryServiceConfig.LookupCategoriesForUrlApiEnabled;
                        Cache.Insert(LookupCategoriesForUrlCacheKey,
                            urlApiEnabled,
                            null,
                            DateTime.Now.AddDays(1),
                            System.Web.Caching.Cache.NoSlidingExpiration,
                            System.Web.Caching.CacheItemPriority.NotRemovable,
                            null);
                    }
                }
            }
            return (bool)HttpRuntime.Cache[LookupCategoriesForUrlCacheKey];
        }

        private static object LookupCategoriesForUrlApiEnabledLock = new object();

        /// <summary>
        /// Call the appropriate Delivery Service 
        /// </summary>
        /// <param name="methodToCall"></param>
        /// <returns></returns>
        private bool CallDeliveryService(DSMethods methodToCall)
        {
            bool passed = false;
            string soapAction = String.Empty;
            string soapEnvelope = String.Empty;
            string serviceUrl = String.Empty;

            switch (methodToCall)
            {
                case DSMethods.LookupObjects:
                    soapAction = "http://schemas.microsoft.com/MRS/Services/2009/07/01/IMrsDeliveryService/LookupObjects";
                    soapEnvelope = LOOKUP_OBJECTS_SOAP_ENVELOPE;
                    break;
                case DSMethods.LookupCategoriesForUrl:
                    soapAction = "http://schemas.microsoft.com/MRS/Services/2009/07/01/IMrsDeliveryService/LookupCategoriesForUrl";
                    soapEnvelope = LOOKUP_CATEGORIES_SOAP_ENVELOPE;
                    break;
                default:
                    return passed;
            }

            serviceUrl = String.Format("{0}://localhost/DeliveryService/service.svc", (UseHTTPSConnection() ? "https" : "http"));

            WebClient wc = new WebClient();

            try
            {
                wc.Headers.Add("user-agent", "QOS Page");
                wc.Headers.Add("Content-Type", "text/xml; charset=utf-8");
                wc.Headers.Add("SOAPAction", soapAction);
                string response = wc.UploadString(serviceUrl, soapEnvelope);

                passed = response.Contains("<operStatus>0</operStatus>");
            }
            catch 
            {
                passed = false;
            }
            return passed;
        }

        // Both of the SOAP strings below use the address http://29360228-29360229.contoso.microsoftmrstest
        private string LOOKUP_OBJECTS_SOAP_ENVELOPE =
            "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><a:LookupObjects xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.Reputation.Services\" xmlns:a=\"http://schemas.microsoft.com/MRS/Services/2009/07/01\"><a:clientId><ClientId>39072d60-0bbd-4612-a466-b96a1dc7fcab</ClientId><CustomerId>0900b81a-6c5f-470d-b7fc-a1a335e56a0d</CustomerId><CustomerVersion>TMG Beta3</CustomerVersion><DeploymentId>513e05e4-d5ee-4bff-b0b1-b17406b6a1d2</DeploymentId></a:clientId><a:requests><LookupRequest><SearchableFields><Metadata><Name>DataBlock</Name><Value a:type=\"xs:base64Binary\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:a=\"http://www.w3.org/2001/XMLSchema-instance\">UlIYAAAAAAABAAAAzg0EAAIAAgAgAGQAOHhwTY/VNGQvp+hdI97uHXZmLRDe2+nqIoDv4AiV+wETw+wfe/3V+AF/DfI0+y7MiwK2HdXLwTK7HKuZCmAtnmgAdAB0AHAAOgAvAC8AMgA5ADMANgAwADIAMgA4AC0AMgA5ADMANgAwADIAMgA5AC4AYwBvAG4AdABvAHMAbwAuAG0AaQBjAHIAbwBzAG8AZgB0AG0AcgBzAHQAZQBzAHQAAAA=</Value></Metadata></SearchableFields><Type>4</Type></LookupRequest></a:requests><b:filter xmlns:a=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\" xmlns:b=\"http://schemas.microsoft.com/MRS/Services/2009/07/01\"><LastStamp a:nil=\"true\" xmlns:a=\"http://www.w3.org/2001/XMLSchema-instance\"/><RequestedData><DataType>Reputation</DataType><DataType>Metadata</DataType></RequestedData><RequestedMetadata><a:string>SHA256</a:string><a:string>Bloom</a:string></RequestedMetadata></b:filter><stateToken a:nil=\"true\" xmlns:a=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.microsoft.com/MRS/Services/2009/07/01\"/></a:LookupObjects></s:Body></s:Envelope>";

        private string LOOKUP_CATEGORIES_SOAP_ENVELOPE =
            "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><LookupCategoriesForUrl xmlns=\"http://schemas.microsoft.com/MRS/Services/2009/07/01\"><clientId xmlns:a=\"http://schemas.datacontract.org/2004/07/Microsoft.Reputation.Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><a:ClientId>4228ac1a-2e5c-40f1-b193-aa5f078c8e83</a:ClientId><a:CustomerId>10f54ef1-f5bd-4fd3-b9c7-15eb086133bb</a:CustomerId><a:CustomerVersion>QOS Page</a:CustomerVersion><a:DeploymentId>59867f97-e895-4397-b1e7-5d7c1326da48</a:DeploymentId></clientId><url>http://29360228-29360229.contoso.microsoftmrstest</url></LookupCategoriesForUrl></s:Body></s:Envelope>";

        /// <summary>
        /// This method is to allow the certificate on localhost to have a different name than the
        /// server name.  This will happen in production as the F5 will have the global server name
        /// but the machine being served will not match the name used for load balancing.
        /// 
        /// This method is called during the SSL handshake to determine if the certs are valid for
        /// use.  This results in an exception with the text of 
        /// "Could not establish trust relationship for the SSL/TLS".  By using this method delegate
        /// we are stating that the certificate is always valid if called by localhost.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="certificate"></param>
        /// <param name="chain"></param>
        /// <param name="sslPolicyErrors"></param>
        /// <returns></returns>
        private bool CheckValidationResult(
            Object sender,
            X509Certificate certificate,
            X509Chain chain,
            System.Net.Security.SslPolicyErrors sslPolicyErrors)
        {
            if ("localhost".Equals(((HttpWebRequest)sender).Headers[HttpRequestHeader.Host], StringComparison.CurrentCultureIgnoreCase))
            {
                return true;
            }
            // using the default
            return defaultCertificateCallback(sender, certificate, chain, sslPolicyErrors);
        }
    }

}

